/**
 * Tests that retryable findAndModify data stored in the `config.image_collection` side collection
 * do not get populated by nodes doing oplog application while in initial sync.
 *
 * This setParameter behavior does not yet exist on earlier versions.
 * @tags: [multiversion_incompatible]
 */
import {ReplSetTest} from "jstests/libs/replsettest.js";

// A secondary cannot compute retryable images during initial sync. Thus we skip db hash checks as
// its expected for config.image_collection to not match.
TestData.skipCheckDBHashes = true;

// Start a single node replica set.
const rst = new ReplSetTest({nodes: 1});
rst.startSet();
rst.initiate();

const dbName = jsTest.name();
const primary = rst.getPrimary();
const primaryDB = primary.getDB(dbName);
const primaryColl = primaryDB['collection'];

primaryColl.insert({_id: 1});

let lsid = UUID();
jsTestLog({
    "Pre-initial sync retryable findAndModify": assert.commandWorked(primaryDB.runCommand({
        findandmodify: primaryColl.getName(),
        lsid: {id: lsid},
        txnNumber: NumberLong(1),
        stmtId: NumberInt(1),
        query: {_id: 1},
        new: false,
        update: {$set: {preInitialSync: true}}
    }))
});

jsTestLog("Adding a new voting node (node1) to the replica set.");
const node1 = rst.add({
    rsConfig: {priority: 1, votes: 1},
    setParameter: {'failpoint.initialSyncHangAfterDataCloning': tojson({mode: 'alwaysOn'})}
});
rst.reInitiate();

jsTestLog("Wait for node1 to hang during initial sync.");
checkLog.containsJson(node1, 21184);

// Perform a findAndModify update that will not be retryable on the node that's concurrently initial
// syncing.
let result = assert.commandWorked(primaryDB.runCommand({
    findandmodify: primaryColl.getName(),
    lsid: {id: lsid},
    txnNumber: NumberLong(2),
    stmtId: NumberInt(1),
    query: {_id: 1},
    new: false,
    update: {$set: {duringInitialSync: true}}
}));
jsTestLog({"Retryable findAndModify during initial sync": result});

// With a separate logical session, perform a findAndModify removal that will not be retryable on
// the node that's concurrently initial syncing.
let otherLsid = UUID();
jsTestLog({
    "Retryable findAndModify removal during initial sync":
        assert.commandWorked(primaryDB.runCommand({
            findandmodify: primaryColl.getName(),
            lsid: {id: otherLsid},
            txnNumber: NumberLong(3),
            stmtId: NumberInt(1),
            query: {_id: 1},
            new: false,
            remove: true
        }))
});

jsTestLog("Resuming initial sync.");
assert.commandWorked(
    node1.adminCommand({configureFailPoint: "initialSyncHangAfterDataCloning", mode: 'off'}));
rst.awaitSecondaryNodes(null, [node1]);

let initialSyncedNode = rst.getSecondary();
rst.stepUp(initialSyncedNode);

result = assert.commandFailedWithCode(initialSyncedNode.getDB(dbName).runCommand({
    findandmodify: primaryColl.getName(),
    lsid: {id: lsid},
    txnNumber: NumberLong(2),
    stmtId: NumberInt(1),
    query: {_id: 1},
    new: false,
    update: {$set: {duringInitialSync: true}}
}),
                                      ErrorCodes.IncompleteTransactionHistory);
// Assert that retrying the update fails.
jsTestLog({
    "Secondary": initialSyncedNode,
    "Data": initialSyncedNode.getDB(dbName)['collection'].findOne(),
    "Image": initialSyncedNode.getDB("config")['image_collection'].findOne({"_id.id": lsid}),
    "retried findAndModify against synced node": result
});

result = assert.commandFailedWithCode(initialSyncedNode.getDB(dbName).runCommand({
    findandmodify: primaryColl.getName(),
    lsid: {id: otherLsid},
    txnNumber: NumberLong(3),
    stmtId: NumberInt(1),
    query: {_id: 1},
    new: false,
    remove: true
}),
                                      ErrorCodes.IncompleteTransactionHistory);
// Assert that retrying the delete fails.
jsTestLog({
    "Secondary": initialSyncedNode,
    "Data": initialSyncedNode.getDB(dbName)['collection'].findOne(),
    "Image": initialSyncedNode.getDB("config")['image_collection'].findOne({"_id.id": otherLsid}),
    "retried delete against synced node": result
});

// There should be two sessions/image entries on the initial syncing node, and both should be
// flagged as invalidated.
assert.eq(2, initialSyncedNode.getDB('config')['image_collection'].count({invalidated: true}));
assert.eq(2,
          initialSyncedNode.getDB('config')['image_collection'].count(
              {invalidatedReason: "initial sync"}));

rst.stopSet();